# This Makefile is intended to be included by another Makefile. It contains a
# number of common functions, variables, recipes. Ideally the calling Makefile
# will only need to define targets for that project.
#
# via https://github.com/PollardBanknote/cppbackport/make.inc
#
# Features:
#  - Shadow building
#  - Auto dependency generation
#  - debug target uses address sanitizer when available (GCC >= 4.8.0),
#    and undefined behavior sanitizer (GCC >=4.9.0)
#  - coloured error messages when available (GCC >=4.9.0)
#  - targets for callgrind, memcheck, insure, profiling and code coverage
#
# Organization
#  - Supporting functions
#  - Definitions of the variables used by this makefile (ex., CXX, WARNINGS)
#  - Predefined configurations of variables (ex., DEBUG)
#  - File type targets (ex., .o)
#  - Named targets (ex., profile)
#  - Inclusion of auto-dependencies
#
# TODO Profile guided optimization
# TODO Precompiled headers
# TODO Static analysis
# TODO Clang support
# TODO -fsanitize=integer for clang
# TODO -fsanitize=thread
# TODO ubsan prints messages to the stderr, does not terminate
# TODO dynamic/static libs
# TODO _GLIBCXX_DEBUG and _GLIBCXX_DEBUG_PEDANTIC
# TODO split up makefile into common functions, variables, configurations, recipes
# TODO -B option for TOOLCHAIN
# TODO CONFIG=RELEASE (strip, for example)
# TODO Different debug levels
# TODO CONFIG=HARDENED including some of the following options
#   TODO -fstack-protector-strong?
#   TODO -Wformat-security
#   TODO -pie -fPIE
#   TODO full relro -Wl,-z,relro,-z,now
# TODO -pipe
# TODO -flto

# Shadow building ==============================================================
# This gets the directory of the calling Makefile (and, presumably, where the
# source code resides

# SHELL ========================================================================
SHELL := /usr/bin/bash
MAKE := /usr/bin/gmake
RM := rm -f

# Functions ====================================================================
gccversion = $(shell $(CXX) -dumpversion | sed -e 's/\.\([0-9][0-9]\)/\1/g' -e 's/\.\([0-9]\)/0\1/g' -e 's/^[0-9]\{3,4\}$$/&00/')
#$(info "INFO --- gccversion $(gccversion)")
gcc42 = $(shell expr $(gccversion) \>= 40200)
gcc48 = $(shell expr $(gccversion) \>= 40800)
gcc49 = $(shell expr $(gccversion) \>= 40900)
gcc51 = $(shell expr $(gccversion) \>= 50100)

# Convert a word to lowercase
lower = $(shell echo $(1) | tr '[:upper:]' '[:lower:]')

# Convert a word to uppercase
upper = $(shell echo $(1) | tr '[:lower:]' '[:upper:]')

# Variables ====================================================================
#
# The basic variables that this makefile uses

OSARCHIVE ?= $(shell uname -s | awk -F'-' '{print $$1}' 2>/dev/null)
OSTYPE ?= $(shell uname -m | awk -F'-' '{print $$1}' 2>/dev/null)
OSRELEASE ?= $(shell uname -r | awk -F'-' '{print $$1}' 2>/dev/null)

# Toolchain --------------------------------------------------------------------
# Which programs to use during compilation

# Path to a different toolchain (ex., ~/bin/gcc-4.9.0)
TOOLCHAIN =

ifndef TOOLCHAIN
# C compiler
CC = gcc

# C++ compiler
CXX = g++

# Archive (.a) program
AR = ar

# Assembler
AS = as

# Ranlib
RANLIB = ranlib

# Linker
LD = $(CXX)
EXPORT_DYNAMIC = --export-dynamic

# Profiler
PROFILER = gprof

else
# Use a particular set of tools
CC = $(TOOLCHAIN)/bin/gcc -B$(TOOLCHAIN)/bin/
CXX = $(TOOLCHAIN)/bin/g++ -B$(TOOLCHAIN)/bin/
AR = $(TOOLCHAIN)/bin/ar
AS = $(TOOLCHAIN)/bin/as
RANLIB = $(TOOLCHAIN)/bin/ranlib
LD = $(CXX)
PROFILER = $(TOOLCHAIN)/bin/gprof

endif

# Misc
VALGRIND = valgrind

# Compiler flags ---------------------------------------------------------------
# Arguments to the toolchain programs

# Directories to search for includes
INCLUDE =

# Any macros that can be defined on the command line
DEFINES = -D_FORTIFY_SOURCE=1
DEFINES += -DCHECK_PTHREAD_RETURN_VALUE
DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -D_LARGEFILE_SOURCE

# Preprocessor flags
CPPFLAGS = $(INCLUDE) $(DEFINES)

# Which version of C++ to compile for
CFLAGS_STANDARD = -pedantic
CXXFLAGS_STANDARD = $(CFLAGS_STANDARD)

# Machine independent options
#-fno-nonansi-builtins
CFLAGS_OPTIONS = -fPIC -export-dynamic

ifeq "$(gcc49)" "1"
CFLAGS_OPTIONS += -fdiagnostics-color
endif

CXXFLAGS_OPTIONS = $(CFLAGS_OPTIONS)
CXXFLAGS_OPTIONS += -fuse-cxa-atexit

# Warnings
CFLAGS_WARNINGS = -Wall -Wextra \
 -Wshadow -Winline -Wredundant-decls -Winit-self \
 -Wpointer-arith -Wcast-align -Wcast-qual \
 -Wwrite-strings -Wconversion -Wsign-compare -Wfloat-equal \
 -Wno-long-long -Wno-unknown-pragmas -Woverflow -Wformat

ifeq "$(gcc48)" "1"
#CFLAGS_WARNINGS += -Wno-c++0x-compat
endif

ifeq "$(gcc51)" "1"
#CFLAGS_WARNINGS += -Wsuggest-attribute=const -Wsuggest-attribute=pure
endif

CXXFLAGS_WARNINGS = $(CFLAGS_WARNINGS)
CXXFLAGS_WARNINGS += -Wold-style-cast -Woverloaded-virtual -Wnon-virtual-dtor

# Architecture flags
CFLAGS_TARGET_ARCH = -march=native
CXXFLAGS_TARGET_ARCH = $(CFLAGS_TARGET_ARCH)

# Level of debugging information to provide
CFLAGS_DEBUGGING = -g
CXXFLAGS_DEBUGGING = $(CFLAGS_DEBUGGING)

# Optimization flags
CFLAGS_OPTIMIZATION = -O2
CXXFLAGS_OPTIMIZATION = $(CFLAGS_OPTIMIZATION)

# Flags to use when compiling C source into object files
CFLAGS = $(CFLAGS_STANDARD) $(CFLAGS_OPTIONS) $(CFLAGS_WARNINGS) $(CFLAGS_TARGET_ARCH) $(CFLAGS_DEBUGGING) $(CFLAGS_OPTIMIZATION)
CXXFLAGS = $(CXXFLAGS_STANDARD) $(CXXFLAGS_OPTIONS) $(CXXFLAGS_WARNINGS) $(CXXFLAGS_TARGET_ARCH) $(CXXFLAGS_DEBUGGING) $(CXXFLAGS_OPTIMIZATION)


# How to build an archive
ARFLAGS = crs

# Linker flags
LDFLAGS =
LDLIBS =

ifdef TOOLCHAIN
# Statically linking
#LDFLAGS += -static-libgcc -static-libstdc++
# Dynamic linking
LDFLAGS += -Wl,-rpath,$(TOOLCHAIN)/lib

LDLIBS += -L$(TOOLCHAIN)/lib
endif


# Printing =====================================================================
# If this is not empty, print messages
VERBOSE =

# Predefined configurations ====================================================
# Combinations of the OPTIMIZATION, etc.
ifdef CONFIG
UCONFIG := $(call upper,$(CONFIG))

# Fast -------------------------------------------------------------------------
# A faster build, primarily for testing
ifeq "$(UCONFIG)" "FAST"
CFLAGS_OPTIMIZATION = -O0
endif

# Tune -------------------------------------------------------------------------
# Heavily optimized for a fast application
ifeq "$(UCONFIG)" "TUNE"
CFLAGS_OPTIMIZATION = -O3 -mtune=native
endif

# Debug ------------------------------------------------------------------------
# Programmer is tracking a bug
ifeq "$(UCONFIG)" "DEBUG"

# Set additional warnings and flags
ifeq "$(gcc51)" "1"
CFLAGS_WARNINGS += -Wstrict-overflow=5
endif

CFLAGS_DEBUGGING = -ggdb3 -fno-omit-frame-pointer -fno-strict-aliasing -fno-default-inline -fno-inline -fno-merge-constants -ffloat-store
# -fno-elide-constructors

# Set the optimization level
ifeq "$(gcc48)" "1"
CFLAGS_OPTIMIZATION = -Og
else
CFLAGS_OPTIMIZATION = -O0
endif

# Turn on address sanitizer
ifeq "$(gcc48)" "1"
CFLAGS_DEBUGGING += -fsanitize=address
LDFLAGS += -fsanitize=address -static-libasan
endif

# Turn on undefined behavior sanitizer and leak sanitizer
ifeq "$(gcc49)" "1"
CFLAGS_DEBUGGING += -fsanitize=undefined -fsanitize=leak
LDFLAGS += -fsanitize=undefined -fsanitize=leak -static-libubsan -static-liblsan
endif
endif

# Profile ----------------------------------------------------------------------
# Programmer wants profile information
ifeq "$(UCONFIG)" "PROFILE"
CFLAGS_OPTIMIZATION = -O2 -pg
LDFLAGS += -pg
endif

# Coverage ---------------------------------------------------------------------
# Programmer wants code coverage information
ifeq "$(UCONFIG)" "COVERAGE"
CFLAGS_OPTIMIZATION = -O0
CFLAGS_DEBUGGING += --coverage -fno-elide-constructors
LDFLAGS += --coverage
endif

endif


# Targets ======================================================================
.PHONY: no_default_goal depend clean_objects clean_profile clean_depend distclean coverage profile memcheck callgrind cachegrind help help_common examples

no_default_goal:
	$(error "The including Makefile hasn't specified a default goal. It should.")

# Dependency generation --------------------------------------------------------
DEPDIR := .deps

# Build object files with automatic dependency generation
# TODO maybe use LINK.cpp from 'make -p'
%.o: %.c
	@mkdir -p $(@D)/$(DEPDIR)
ifeq ($(VERBOSE),)
	@echo [CXX] $@
	@$(CC) -c $(CFLAGS) $(CPPFLAGS) -MMD -MP -MF $(@D)/$(DEPDIR)/$(*F).d -o $@ $<
else
	$(CC) -c $(CFLAGS) $(CPPFLAGS) -MMD -MP -MF $(@D)/$(DEPDIR)/$(*F).d -o $@ $<
endif

%.o: %.cpp
	@mkdir -p $(@D)/$(DEPDIR)
ifeq ($(VERBOSE),)
	@echo [CXX] $@
	@$(CXX) -c $(CXXFLAGS) $(CPPFLAGS) -MMD -MP -MF $(@D)/$(DEPDIR)/$(*F).d -o $@ $<
else
	$(CXX) -c $(CXXFLAGS) $(CPPFLAGS) -MMD -MP -MF $(@D)/$(DEPDIR)/$(*F).d -o $@ $<
endif

%.so:
ifeq ($(VERBOSE),)
	@echo [LD] $@
	@$(LD) -shared $(LDFLAGS) $^ $(LDLIBS) -o $@
else
	$(LD) -shared $(LDFLAGS) $^ $(LDLIBS) -o $@
endif

%.a:
ifeq ($(VERBOSE),)
	@echo [AR] $@
	@$(AR) $(ARFLAGS) $@ $^ > /dev/null
	@$(RANLIB) $@ > /dev/null
else
	$(AR) $(ARFLAGS) $@ $^ > /dev/null
	$(RANLIB) $@ > /dev/null
endif

# A script for combining .a's. Dependency list for the mri should be
# subdirectories containing static libs. Name of the output library
# is inferred from the mri name (abc.mri produces libabc.a)
%.mri:
	@echo 'CREATE $(patsubst %.mri,lib%.a,$@)' > $@
	@for dir in $^; do echo ADDLIB $$dir >> $@; done
	@echo 'SAVE' >> $@


# Code coverage ----------------------------------------------------------------
coverage:
ifdef COVERAGE_OUT
	@TMPFILE=`mktemp`; lcov --capture --directory . --output-file $$TMPFILE; genhtml $$TMPFILE --output-directory $(COVERAGE_OUT); $(RM) $$TMPFILE
else
	$(error You have not set COVERAGE_OUT)
endif

# Profiling --------------------------------------------------------------------
profile:
ifdef PROFILE_TARGET
	$(PROFILE_TARGET) $(PROFILE_TARGET_ARGS)
ifdef PROFILE_OUT
	@$(PROFILER) -c $(PROFILE_TARGET) > $(PROFILE_OUT)
else
	$(error You have not specified PROFILE_OUT)
endif
else
	$(error You have not specified PROFILE_TARGET)
endif

# Memcheck ---------------------------------------------------------------------
# User should set MEMCHECK_TARGET, MEMCHECK_TARGET_ARGS, and a recipe like:
# memcheck: $(MEMCHECK_TARGET)
memcheck:
	$(VALGRIND) --tool=memcheck -v --read-var-info=yes --track-origins=yes --show-reachable=yes --leak-check=full --read-inline-info=yes --keep-stacktraces=alloc-and-free $(VALGRIND_TARGET) $(VALGRIND_TARGET_ARGS) 2> $(MEMCHECK_OUT)

# Callgrind --------------------------------------------------------------------
# User should set CALLGRIND_TARGET, CALLGRIND_TARGET_ARGS, and a recipe like:
# callgrind: $(CALLGRIND_TARGET)
callgrind:
	$(VALGRIND) --tool=callgrind $(VALGRIND_TARGET) $(VALGRIND_TARGET_ARGS)

cachegrind:
	$(VALGRIND) --tool=cachegrind --branch-sim=yes $(VALGRIND_TARGET) $(VALGRIND_TARGET_ARGS)

# Clean targets ----------------------------------------------------------------
# remove all .o files
clean_objects:
ifeq ($(VERBOSE),)
	@find ./ -name '*.o' -exec $(RM) '{}' \;
else
	find ./ -name '*.o' -exec $(RM) '{}' \;
endif

# remove all files left by gprof
clean_profile:
ifeq ($(VERBOSE),)
	@$(RM) profile
	@find ./ \( -name '*.gcda' -o -name '*.gcno' \) -exec $(RM) '{}' \;
else
	$(RM) profile
	find ./ \( -name '*.gcda' -o -name '*.gcno' \) -exec $(RM) '{}' \;
endif

clean_depend:
ifeq ($(VERBOSE),)
	@find ./ -depth -name $(DEPDIR) -type d -exec $(RM) -r '{}' \;
else
	find ./ -depth -name $(DEPDIR) -type d -exec $(RM) -r '{}' \;
endif

depend: clean clean_depend

distclean: depend

# Documentation ----------------------------------------------------------------
help_common:
	@echo "==================================================================="
	@echo "Makefile variables and their defaults:"
	@echo "  MAKE_COMMAND    ($(MAKE_COMMAND))"
	@echo "  MAKE_VERSION    ($(MAKE_VERSION))"
	@echo "  SHELL           ($(SHELL))"
	@echo "  MAKE            ($(MAKE))"
	@echo "  RM              ($(RM))"
	@echo
	@echo "Here are some standard Makefile variables and their defaults:"
	@echo "  CC        ($(CC))"
	@echo "  CXX       ($(CXX))"
	@echo "  AR        ($(AR))"
	@echo "  AS        ($(AS))"
	@echo "  RANLIB    ($(RANLIB))"
	@echo "  LD        ($(LD))"
	@echo "  PROFILER  ($(PROFILER))"
	@echo "  CPPFLAGS  ($(CPPFLAGS))"
	@echo "  CFLAGS    ($(CFLAGS))"
	@echo "  CXXFLAGS  ($(CXXFLAGS))"
	@echo "  ARFLAGS   ($(ARFLAGS))"
	@echo "  LDFLAGS   ($(LDFLAGS))"
	@echo "  LDLIBS    ($(LDLIBS))"
	@echo
	@echo "You can set the TOOLCHAIN variable to use a different compiler,"
	@echo "linker, profiler. Ex., (~/bin/gcc-4.9.0)"
	@echo
	@echo "The CFLAGS variable is comprised of a number of other variables:"
	@echo "  CFLAGS_STANDARD       ($(CFLAGS_STANDARD))"
	@echo "  CFLAGS_OPTIONS        ($(CFLAGS_OPTIONS))"
	@echo "  CFLAGS_WARNINGS       ($(CFLAGS_WARNINGS))"
	@echo "  CFLAGS_TARGET_ARCH    ($(CFLAGS_TARGET_ARCH))"
	@echo "  CFLAGS_DEBUGGING      ($(CFLAGS_DEBUGGING))"
	@echo "  CFLAGS_OPTIMIZATION   ($(CFLAGS_OPTIMIZATION))"
	@echo
	@echo "The CXXFLAGS variable is comprised of a number of other variables:"
	@echo "  CXXFLAGS_STANDARD     ($(CXXFLAGS_STANDARD))"
	@echo "  CXXFLAGS_OPTIONS      ($(CXXFLAGS_OPTIONS))"
	@echo "  CXXFLAGS_WARNINGS     ($(CXXFLAGS_WARNINGS))"
	@echo "  CXXFLAGS_TARGET_ARCH  ($(CXXFLAGS_TARGET_ARCH))"
	@echo "  CXXFLAGS_DEBUGGING    ($(CXXFLAGS_DEBUGGING))"
	@echo "  CXXFLAGS_OPTIMIZATION ($(CXXFLAGS_OPTIMIZATION))"
	@echo
	@echo "For convenience, several preconfigured options are available by"
	@echo "setting the CONFIG variale:"
	@echo "  DEBUG    - Turns on debugging info; turns down optimization; enables"
	@echo "             address sanitizer, etc. if available"
	@echo "  PROFILE  - Keeps optimization for release build; adds debugging"
	@echo "             and gprof flags"
	@echo "  COVERAGE - Don't inline functions"
	@echo "  TUNE     - High optimization level; CPU-specific instructions"
	@echo "  FAST     - A fast compilation with low optimization level"
	@echo
	@echo "==================================================================="
	@echo "Here are some special targets and variables that affect them"
	@echo
	@echo "  profile    - First build a target with CONFIG=PROFILE and run it."
	@echo "       PROFILE_TARGET the name of the program to profile"
	@echo "            (default: $(PROFILE_TARGET))"
	@echo "       PROFILE_TARGET_ARGS arguments to the program to profile"
	@echo "            (default: $(PROFILE_TARGET_ARGS))"
	@echo "       PROFILE_OUT the file to place the profile data in"
	@echo "            (default: $(PROFILE_OUT))"
	@echo "       PROFILER Path to profiler"
	@echo "           (default: $(PROFILER))"
	@echo "  coverage   - First build a target with CONFIG=COVERAGE and run it."
	@echo "       COVERAGE_OUT specifies where to put the resulting web pages"
	@echo "           (default: $(COVERAGE_OUT))"
	@echo
	@echo "  memcheck   - Run memcheck for VALGRIND_TARGET, testing memory access"
	@echo "  callgrind  - Run callgrind for VALGRIND_TARGET, testing performance"
	@echo "  cachegrind - Run cachegrind for VALGRIND_TARGET, testing cache"
	@echo "       VALGRIND_TARGET The program to check"
	@echo "           (default: $(VALGRIND_TARGET))"
	@echo "       VALGRIND_TARGET_ARGS The arguments to VALGRIND_TARGET"
	@echo "           (default: $(VALGRIND_TARGET_ARGS))"
	@echo "       MEMCHECK_OUT Where to put the memcheck results"
	@echo "           (default: $(MEMCHECK_OUT))"
	@echo "==================================================================="

help: help_common

# ==============================================================================
# Include dependencies which are stored as .d files in the $(DEPDIR) directories
# TODO prefer -wholename to -path, but old version of find ./ does not have wholename
-include $(shell find ./ -path '*/$(DEPDIR)/*.d')

